package org.dslforge.aurum;

import static java.util.Arrays.asList;
import static java.util.Collections.singleton;
import static org.dslforge.aurum.SemanticAction.Type.RecognizeTerminal;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Stack;

import org.dslforge.aurum.Automata;
import org.dslforge.aurum.CharSet;
import org.dslforge.aurum.Pattern;
import org.dslforge.aurum.SemanticAction;
import org.dslforge.aurum.Automata.Transition;

class LexicalHelper {
	static SemanticAction DUMMY = new SemanticAction(RecognizeTerminal, "$DUMMY", Arrays
			.asList("$DUMMY"));

	static void assertPatternMatches(Pattern pattern, String source) {
		pattern.automata.setAccept(pattern.tail, DUMMY);
		assertNFARecognizes(pattern.automata, source);
	}

	static void assertPatternNotMatches(Pattern pattern, String source) {
		pattern.automata.setAccept(pattern.tail, DUMMY);
		assertNFANotRecognizes(pattern.automata, source);
	}

	static void assertNFARecognizes(Automata<CharSet, ?> nfa, String source) {
		Set<Integer> result = closure(nfa, asList(0));
		for (int i = 0; i < source.length(); i++)
			result = move(nfa, result, source.charAt(i));
		assertFalse(Collections.disjoint(result, nfa.acceptStates.keySet()));
	}

	static void assertNFANotRecognizes(Automata<CharSet, ?> nfa, String source) {
		Set<Integer> result = closure(nfa, asList(0));
		for (int i = 0; i < source.length(); i++)
			result = move(nfa, result, source.charAt(i));
		assertTrue(Collections.disjoint(result, nfa.acceptStates.keySet()));
	}

	static void assertDFARecognizes(Automata<CharSet, ?> dfa, String source) {
		int current = 0;
		for (int i = 0; i < source.length(); i++)
			for (Transition<CharSet> transition : dfa.transitionTable.get(current))
				if (transition.symbols.contains(source.charAt(i))) {
					current = transition.destination;
					break;
				}
		assertTrue(dfa.acceptStates.containsKey(current));
	}

	static void assertDFANotRecognizes(Automata<CharSet, ?> dfa, String source) {
		int current = 0;
		for (int i = 0; i < source.length(); i++)
			for (Transition<CharSet> transition : dfa.transitionTable.get(current))
				if (transition.symbols.contains(source.charAt(i))) {
					current = transition.destination;
					break;
				}
		assertFalse(dfa.acceptStates.containsKey(current));
	}

	static void assertActionTriggered(SemanticAction excepted,
			Automata<CharSet, Collection<SemanticAction>> dfa, String lexeme) {
		int current = 0;
		for (int i = 0; i < lexeme.length(); i++)
			for (Transition<CharSet> transition : dfa.transitionTable.get(current))
				if (transition.symbols.contains(lexeme.charAt(i))) {
					current = transition.destination;
					break;
				}
		assertTrue(dfa.acceptStates.get(current).contains(excepted));
	}

	static Set<Integer> move(Automata<CharSet, ?> automata, Set<Integer> states, char inputSymbol) {
		Set<Integer> result = new HashSet<Integer>();
		for (Integer state : states) {
			Integer destination = move(automata.transitionTable.get(state), inputSymbol);
			if (destination != null)
				result.addAll(closure(automata, singleton(destination)));
		}
		return result;
	}

	static Integer move(List<Transition<CharSet>> transitions, char inputSymbol) {
		for (Transition<CharSet> transition : transitions)
			if (transition.symbols.contains(inputSymbol))
				return transition.destination;
		return null;
	}

	static Set<Integer> closure(Automata<CharSet, ?> automata, Collection<Integer> states) {
		Set<Integer> closure = new HashSet<Integer>();
		Stack<Integer> unvisited = new Stack<Integer>();
		for (Integer state : states) {
			closure.add(state);
			unvisited.add(state);
		}
		while (!unvisited.isEmpty())
			for (Transition<CharSet> transition : automata.transitionTable.get(unvisited.pop()))
				if (transition.symbols == CharSet.EPSILON
						&& !closure.contains(transition.destination)) {
					closure.add(transition.destination);
					unvisited.push(transition.destination);
				}
		return closure;
	}
}
